package com.codeckz.library_face.utils

import android.content.Context
import android.graphics.*
import androidx.camera.core.ImageProxy
import java.io.ByteArrayOutputStream


/**
 *@packageName com.codeckz.library_face.utils
 *@author kzcai
 *@date 2022/1/7
 */

fun Bitmap.toByteArray(quality: Int = 100): ByteArray {
    val baos = ByteArrayOutputStream()
    compress(Bitmap.CompressFormat.JPEG, quality, baos)
    return baos.toByteArray()
}

fun ByteArray.toBitmap(): Bitmap {
    val options = BitmapFactory.Options()

    return BitmapFactory.decodeByteArray(this, 0, size)
}

fun Bitmap.cutRect(context: Context, rect: Rect, offset: Int = 0): Bitmap {
    val sw = ScreenUtils.getScreenWidth(context)
    val sh = ScreenUtils.getScreenHeight(context)
    val st = rect.top
    val sl = rect.left
    val sr = rect.right
    val sb = rect.bottom

    val pl = (sl * width / sw) - offset
    val pt = (st * height / sh) - offset
    val pr = (sr * width / sw) + offset
    val pb = (sb * height / sh) + offset
    return Bitmap.createBitmap(this, pl, pt, pr - pl, pb - pt)
}

fun Bitmap.adjustPhotoRotation(process:Int):Bitmap{
    return NV21ToBitmap.adjustPhotoRotation(this,process)
}

fun Bitmap.cutRectSquareCenter(): Bitmap {
    var left = 0
    var top = 0
    var width = 0
    var height = 0
    val bw = this.width
    val bh = this.height
    if (bw > bh) {
        top = 0
        height = bh
        left = bw / 2 - bh / 2
        width = bh
    } else {
        left = 0
        width = bw
        top = bh / 2 - bw / 2
        height = bw
    }
    return Bitmap.createBitmap(this, left, top, width, height)
}

fun Bitmap.addBitmap(context: Context, rect: Rect): Bitmap {
    val config = Bitmap.Config.RGB_565
    val sw = ScreenUtils.getScreenWidth(context)
    val sh = ScreenUtils.getScreenHeight(context)
    val bitmap = Bitmap.createBitmap(sw, sh, config)
    val cavans = Canvas(bitmap)
    cavans.drawBitmap(this, null, rect, Paint())
    return bitmap
}

fun Bitmap.addBitmap2(): Bitmap {
    val config = Bitmap.Config.RGB_565
    val sw = this.width+50
    val sh = this.height+100
    val bitmap = Bitmap.createBitmap(sw, sh, config)
    val cavans = Canvas(bitmap)
    var rect = Rect(50,100,this.width,this.height)
    cavans.drawBitmap(this, null, rect, Paint())
    return bitmap
}

fun Bitmap.cutCheckRect(context: Context, rect: Rect): Bitmap {
    val sw = ScreenUtils.getScreenWidth(context)
    val sh = ScreenUtils.getScreenHeight(context)
    val st = rect.top
    val sl = rect.left
    val sr = rect.right
    val sb = rect.bottom

    var pl = sl * width / sw
    var pt = st * height / sh
    var pr = sr * width / sw
    var pb = sb * height / sh

    while (pr - pl < 350) {
        pt -= 5
        pl -= 1
        pr += 1
        pb += 5
    }
    return Bitmap.createBitmap(this, pl, pt, pr - pl, pb - pt)
}

fun Rect.bitmapCheckRect(context: Context, bitmap: Bitmap, offset: Int = 0): Rect {
    val sw = ScreenUtils.getScreenWidth(context)
    val sh = ScreenUtils.getScreenHeight(context)
    val st = top
    val sl = left
    val sr = right
    val sb = bottom

    val pl = (sl * bitmap.width / sw) - offset
    val pt = (st * bitmap.height / sh) - offset
    val pr = (sr * bitmap.width / sw) + offset
    val pb = (sb * bitmap.height / sh) + offset

    return Rect(pl, pt, pr, pb)
}


fun Bitmap.fitScreen(context: Context, degrees: Float): Bitmap {
    val matrix = Matrix()
    matrix.setRotate(
        degrees
    )
    val newBM = Bitmap.createBitmap(
        this,
        0,
        0,
        width,
        height,
        matrix,
        false
    )
    val sw = ScreenUtils.getScreenWidth(context)
    val sh = ScreenUtils.getScreenHeight(context) - ScreenUtils.getNavigationBarHeight(context)
    val pw = newBM.width
    val ph = newBM.height
    val rw = ((sw * ph).toDouble() / sh).toInt()
    val left = (pw - rw) / 2
    val right = pw - left
    val cutBM = Bitmap.createBitmap(newBM, left, 0, rw, ph)
    return cutBM
}

fun ImageProxy.toBitmap(): Bitmap? {
    val nv21 = yuv420888ToNv21(this)
    val yuvImage = YuvImage(nv21, ImageFormat.NV21, width, height, null)
    return yuvImage.toBitmap()
}


fun ImageProxy.toByteArray(): ByteArray? {
    val nv21 = yuv420888ToNv21(this)
    val yuvImage = YuvImage(nv21, ImageFormat.NV21, width, height, null)
    return yuvImage.toByteArray()
}

fun YuvImage.toBitmap(): Bitmap? {
    val out = ByteArrayOutputStream()
    if (!compressToJpeg(Rect(0, 0, width, height), 100, out))
        return null
    val imageBytes: ByteArray = out.toByteArray()
    val op = BitmapFactory.Options()
    op.inPreferredConfig = Bitmap.Config.ARGB_8888
    return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size, op)
}

fun Bitmap.changeBrightness(brightness: Float): Bitmap {
    val bmp = Bitmap.createBitmap(
        width, height,
        Bitmap.Config.ARGB_8888
    )
    val cMatrix = ColorMatrix()
    cMatrix.set(
        floatArrayOf(
            1f,
            0f,
            0f,
            0f,
            brightness,
            0f,
            1f,
            0f,
            0f,
            brightness,
            0f,
            0f,
            1f,
            0f,
            brightness,
            0f,
            0f,
            0f,
            1f,
            0f
        )
    )

    val paint = Paint()


    paint.colorFilter = ColorMatrixColorFilter(cMatrix)
    val canvas = Canvas(bmp)
    canvas.drawBitmap(this, 0f, 0f, paint)
    return bmp
}

private fun YuvImage.toByteArray(): ByteArray? {
    val out = ByteArrayOutputStream()
    if (!compressToJpeg(Rect(0, 0, width, height), 100, out))
        return null
    return out.toByteArray()
}

private fun yuv420888ToNv21(image: ImageProxy): ByteArray {
    val pixelCount = image.cropRect.width() * image.cropRect.height()
    val pixelSizeBits = ImageFormat.getBitsPerPixel(ImageFormat.YUV_420_888)
    val outputBuffer = ByteArray(pixelCount * pixelSizeBits / 8)
    imageToByteBuffer(image, outputBuffer, pixelCount)
    return outputBuffer
}

private fun imageToByteBuffer(image: ImageProxy, outputBuffer: ByteArray, pixelCount: Int) {
    assert(image.format == ImageFormat.YUV_420_888)

    val imageCrop = image.cropRect
    val imagePlanes = image.planes

    imagePlanes.forEachIndexed { planeIndex, plane ->
        // How many values are read in input for each output value written
        // Only the Y plane has a value for every pixel, U and V have half the resolution i.e.
        //
        // Y Plane            U Plane    V Plane
        // ===============    =======    =======
        // Y Y Y Y Y Y Y Y    U U U U    V V V V
        // Y Y Y Y Y Y Y Y    U U U U    V V V V
        // Y Y Y Y Y Y Y Y    U U U U    V V V V
        // Y Y Y Y Y Y Y Y    U U U U    V V V V
        // Y Y Y Y Y Y Y Y
        // Y Y Y Y Y Y Y Y
        // Y Y Y Y Y Y Y Y
        val outputStride: Int

        // The index in the output buffer the next value will be written at
        // For Y it's zero, for U and V we start at the end of Y and interleave them i.e.
        //
        // First chunk        Second chunk
        // ===============    ===============
        // Y Y Y Y Y Y Y Y    V U V U V U V U
        // Y Y Y Y Y Y Y Y    V U V U V U V U
        // Y Y Y Y Y Y Y Y    V U V U V U V U
        // Y Y Y Y Y Y Y Y    V U V U V U V U
        // Y Y Y Y Y Y Y Y
        // Y Y Y Y Y Y Y Y
        // Y Y Y Y Y Y Y Y
        var outputOffset: Int

        when (planeIndex) {
            0 -> {
                outputStride = 1
                outputOffset = 0
            }
            1 -> {
                outputStride = 2
                // For NV21 format, U is in odd-numbered indices
                outputOffset = pixelCount + 1
            }
            2 -> {
                outputStride = 2
                // For NV21 format, V is in even-numbered indices
                outputOffset = pixelCount
            }
            else -> {
                // Image contains more than 3 planes, something strange is going on
                return@forEachIndexed
            }
        }

        val planeBuffer = plane.buffer
        val rowStride = plane.rowStride
        val pixelStride = plane.pixelStride

        // We have to divide the width and height by two if it's not the Y plane
        val planeCrop = if (planeIndex == 0) {
            imageCrop
        } else {
            Rect(
                imageCrop.left / 2,
                imageCrop.top / 2,
                imageCrop.right / 2,
                imageCrop.bottom / 2
            )
        }

        val planeWidth = planeCrop.width()
        val planeHeight = planeCrop.height()

        // Intermediate buffer used to store the bytes of each row
        val rowBuffer = ByteArray(plane.rowStride)

        // Size of each row in bytes
        val rowLength = if (pixelStride == 1 && outputStride == 1) {
            planeWidth
        } else {
            // Take into account that the stride may include data from pixels other than this
            // particular plane and row, and that could be between pixels and not after every
            // pixel:
            //
            // |---- Pixel stride ----|                    Row ends here --> |
            // | Pixel 1 | Other Data | Pixel 2 | Other Data | ... | Pixel N |
            //
            // We need to get (N-1) * (pixel stride bytes) per row + 1 byte for the last pixel
            (planeWidth - 1) * pixelStride + 1
        }

        for (row in 0 until planeHeight) {
            // Move buffer position to the beginning of this row
            planeBuffer.position(
                (row + planeCrop.top) * rowStride + planeCrop.left * pixelStride
            )

            if (pixelStride == 1 && outputStride == 1) {
                // When there is a single stride value for pixel and output, we can just copy
                // the entire row in a single step
                planeBuffer.get(outputBuffer, outputOffset, rowLength)
                outputOffset += rowLength
            } else {
                // When either pixel or output have a stride > 1 we must copy pixel by pixel
                planeBuffer.get(rowBuffer, 0, rowLength)
                for (col in 0 until planeWidth) {
                    outputBuffer[outputOffset] = rowBuffer[col * pixelStride]
                    outputOffset += outputStride
                }
            }
        }
    }
}